msg_tool\scripts\circus\archive/
dat.rs1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use anyhow::Result;
6use std::io::{Read, Seek, SeekFrom};
7use std::sync::{Arc, Mutex};
8
9#[derive(Debug)]
10pub struct DatArchiveBuilder {}
12
13impl DatArchiveBuilder {
14 pub fn new() -> Self {
16 Self {}
17 }
18}
19
20impl ScriptBuilder for DatArchiveBuilder {
21 fn default_encoding(&self) -> Encoding {
22 Encoding::Cp932
23 }
24
25 fn default_archive_encoding(&self) -> Option<Encoding> {
26 Some(Encoding::Cp932)
27 }
28
29 fn build_script(
30 &self,
31 data: Vec<u8>,
32 _filename: &str,
33 _encoding: Encoding,
34 archive_encoding: Encoding,
35 config: &ExtraConfig,
36 _archive: Option<&Box<dyn Script>>,
37 ) -> Result<Box<dyn Script + Send + Sync>> {
38 Ok(Box::new(DatArchive::new(
39 MemReader::new(data),
40 archive_encoding,
41 config,
42 )?))
43 }
44
45 fn build_script_from_file(
46 &self,
47 filename: &str,
48 _encoding: Encoding,
49 archive_encoding: Encoding,
50 config: &ExtraConfig,
51 _archive: Option<&Box<dyn Script>>,
52 ) -> Result<Box<dyn Script + Send + Sync>> {
53 if filename == "-" {
54 let data = crate::utils::files::read_file(filename)?;
55 Ok(Box::new(DatArchive::new(
56 MemReader::new(data),
57 archive_encoding,
58 config,
59 )?))
60 } else {
61 let f = std::fs::File::open(filename)?;
62 let reader = std::io::BufReader::new(f);
63 Ok(Box::new(DatArchive::new(reader, archive_encoding, config)?))
64 }
65 }
66
67 fn build_script_from_reader<'a>(
68 &self,
69 reader: Box<dyn ReadSeek + Send + Sync + 'a>,
70 _filename: &str,
71 _encoding: Encoding,
72 archive_encoding: Encoding,
73 config: &ExtraConfig,
74 _archive: Option<&Box<dyn Script>>,
75 ) -> Result<Box<dyn Script + Send + Sync + 'a>> {
76 Ok(Box::new(DatArchive::new(reader, archive_encoding, config)?))
77 }
78
79 fn extensions(&self) -> &'static [&'static str] {
80 &["dat"]
81 }
82
83 fn script_type(&self) -> &'static ScriptType {
84 &ScriptType::CircusDat
85 }
86
87 fn is_archive(&self) -> bool {
88 true
89 }
90
91 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
92 is_this_format(&buf[..buf_len]).ok()
93 }
94}
95
96#[derive(Debug, Clone)]
97struct DatFileHeader {
98 name: String,
99 offset: u32,
100 size: u32,
101}
102
103#[derive(Debug)]
104struct Entry<T: Read + Seek> {
105 header: DatFileHeader,
106 reader: Arc<Mutex<T>>,
107 pos: usize,
108 script_type: Option<ScriptType>,
109}
110
111impl<T: Read + Seek + std::fmt::Debug + Send + Sync> ArchiveContent for Entry<T> {
112 fn name(&self) -> &str {
113 &self.header.name
114 }
115
116 fn size(&self) -> Option<u64> {
117 Some(self.header.size as u64)
118 }
119
120 fn script_type(&self) -> Option<&ScriptType> {
121 self.script_type.as_ref()
122 }
123
124 fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
125 Ok(Box::new(self))
126 }
127}
128
129impl<T: Read + Seek> Read for Entry<T> {
130 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
131 let mut reader = self.reader.lock().map_err(|e| {
132 std::io::Error::new(
133 std::io::ErrorKind::Other,
134 format!("Failed to lock mutex: {}", e),
135 )
136 })?;
137 reader.seek(SeekFrom::Start(self.header.offset as u64 + self.pos as u64))?;
138 let bytes_read = buf.len().min(self.header.size as usize - self.pos);
139 if bytes_read == 0 {
140 return Ok(0);
141 }
142 let bytes_read = reader.read(&mut buf[..bytes_read])?;
143 self.pos += bytes_read;
144 Ok(bytes_read)
145 }
146}
147
148impl<T: Read + Seek> Seek for Entry<T> {
149 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
150 let new_pos = match pos {
151 SeekFrom::Start(offset) => offset as usize,
152 SeekFrom::End(offset) => {
153 if offset < 0 {
154 if (-offset) as usize > self.header.size as usize {
155 return Err(std::io::Error::new(
156 std::io::ErrorKind::InvalidInput,
157 "Seek from end exceeds file length",
158 ));
159 }
160 self.header.size as usize - (-offset) as usize
161 } else {
162 self.header.size as usize + offset as usize
163 }
164 }
165 SeekFrom::Current(offset) => {
166 if offset < 0 {
167 if (-offset) as usize > self.pos {
168 return Err(std::io::Error::new(
169 std::io::ErrorKind::InvalidInput,
170 "Seek from current exceeds current position",
171 ));
172 }
173 self.pos.saturating_sub((-offset) as usize)
174 } else {
175 self.pos + offset as usize
176 }
177 }
178 };
179 self.pos = new_pos;
180 Ok(self.pos as u64)
181 }
182
183 fn stream_position(&mut self) -> std::io::Result<u64> {
184 Ok(self.pos as u64)
185 }
186}
187
188#[derive(Debug)]
189pub struct DatExtraInfo {
191 pub name_len: usize,
193}
194
195impl AnyDebug for DatExtraInfo {
196 fn as_any(&self) -> &dyn std::any::Any {
197 self
198 }
199}
200
201#[derive(Debug)]
202pub struct DatArchive<'b, T: Read + Seek + std::fmt::Debug + 'b> {
204 reader: Arc<Mutex<T>>,
205 entries: Vec<DatFileHeader>,
206 name_len: usize,
207 _mark: std::marker::PhantomData<&'b ()>,
208}
209
210const NAME_LEN: [usize; 3] = [0x24, 0x30, 0x3C];
211
212impl<'b, T: Read + Seek + std::fmt::Debug + 'b> DatArchive<'b, T> {
213 pub fn new(mut reader: T, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
219 let (name_len, entries) = Self::read_all_index(&mut reader, encoding)?;
220 let reader = Arc::new(Mutex::new(reader));
221 Ok(Self {
222 reader,
223 entries,
224 name_len,
225 _mark: std::marker::PhantomData,
226 })
227 }
228
229 fn read_all_index(reader: &mut T, encoding: Encoding) -> Result<(usize, Vec<DatFileHeader>)> {
230 for &name_len in &NAME_LEN {
231 match Self::read_index(reader, encoding, name_len) {
232 Ok(entries) => return Ok((name_len, entries)),
233 Err(_) => continue,
234 }
235 }
236 Err(anyhow::anyhow!("Failed to read DAT index"))
237 }
238
239 fn read_index(
240 reader: &mut T,
241 encoding: Encoding,
242 name_len: usize,
243 ) -> Result<Vec<DatFileHeader>> {
244 reader.rewind()?;
245 let mut count = reader.read_u32()?;
246 let index_size = (name_len + 4) * count as usize;
247 count -= 1;
248 let mut entries = Vec::with_capacity(count as usize);
249 let mut next_offset = reader.peek_u32_at(4 + name_len as u64)?;
250 if (next_offset as usize) < index_size + 4 {
251 return Err(anyhow::anyhow!("Invalid next_offset"));
252 }
253 let first_size = reader.peek_u32_at(name_len as u64)?;
254 let second_offset = reader.peek_u32_at(8 + name_len as u64 * 2)?;
255 if second_offset - next_offset == first_size {
256 return Err(anyhow::anyhow!("Invalid second_offset"));
257 }
258 let file_len = reader.stream_length()?;
259 for i in 0..count {
260 let name = reader.read_fstring(name_len, encoding, true)?;
261 if name.is_empty() {
262 return Err(anyhow::anyhow!("Empty file name in DAT archive"));
263 }
264 let offset = next_offset;
265 if i + 1 == count {
266 next_offset = file_len as u32;
267 } else {
268 next_offset = reader.peek_u32_at((name_len as u64 + 4) * (i as u64 + 2))?;
269 }
270 if next_offset < offset {
271 return Err(anyhow::anyhow!("Invalid offset in DAT archive"));
272 }
273 let size = next_offset - offset;
274 if offset < index_size as u32 || offset + size > file_len as u32 {
275 return Err(anyhow::anyhow!("Invalid offset or size in DAT archive"));
276 }
277 let header = DatFileHeader { name, offset, size };
278 entries.push(header);
279 reader.seek_relative(4)?;
280 }
281 Ok(entries)
282 }
283}
284
285impl<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> Script for DatArchive<'b, T> {
286 fn default_output_script_type(&self) -> OutputScriptType {
287 OutputScriptType::Json
288 }
289
290 fn default_format_type(&self) -> FormatOptions {
291 FormatOptions::None
292 }
293
294 fn is_archive(&self) -> bool {
295 true
296 }
297
298 fn iter_archive_filename<'a>(
299 &'a self,
300 ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
301 Ok(Box::new(self.entries.iter().map(|e| Ok(e.name.clone()))))
302 }
303
304 fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
305 Ok(Box::new(self.entries.iter().map(|e| Ok(e.offset as u64))))
306 }
307
308 fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + Send + Sync + 'a>> {
309 if index >= self.entries.len() {
310 return Err(anyhow::anyhow!(
311 "Index out of bounds: {} (max: {})",
312 index,
313 self.entries.len()
314 ));
315 }
316 let entry = &self.entries[index];
317 let mut entry = Entry {
318 header: entry.clone(),
319 reader: self.reader.clone(),
320 pos: 0,
321 script_type: None,
322 };
323 let mut buf = [0; 32];
324 let readed = match entry.read(&mut buf) {
325 Ok(readed) => readed,
326 Err(e) => {
327 return Err(anyhow::anyhow!(
328 "Failed to read entry '{}': {}",
329 entry.header.name,
330 e
331 ));
332 }
333 };
334 entry.pos = 0;
335 entry.script_type = detect_script_type(&buf, readed, &entry.header.name);
336 Ok(Box::new(entry))
337 }
338
339 fn extra_info<'a>(&'a self) -> Option<Box<dyn AnyDebug + 'a>> {
340 Some(Box::new(DatExtraInfo {
341 name_len: self.name_len,
342 }))
343 }
344}
345
346fn detect_script_type(_buf: &[u8], _buf_len: usize, _filename: &str) -> Option<ScriptType> {
347 #[cfg(feature = "circus-img")]
348 if _buf_len >= 4 && _buf.starts_with(b"CRXG") {
349 return Some(ScriptType::CircusCrx);
350 }
351 #[cfg(feature = "circus-audio")]
352 if _buf_len >= 4 && _buf.starts_with(b"XPCM") {
353 return Some(ScriptType::CircusPcm);
354 }
355 None
356}
357
358fn is_this_format_name_len(buf: &[u8], name_len: usize) -> Result<u8> {
359 let mut reader = MemReaderRef::new(buf);
360 let count = reader.read_u32()? as usize;
361 let index_size = (name_len + 4) * count;
362 let mut score = if count > 0 && count < 1000 { 5 } else { 0 };
363 let mcount = ((buf.len() - 4) / (name_len + 4)).min(count - 1);
364 score += ((mcount / 2).min(10)) as u8;
365 if mcount == 0 {
366 return Err(anyhow::anyhow!("No entries found in DAT archive"));
367 }
368 let mut next_offset = reader.cpeek_u32_at(4 + name_len as u64)?;
369 if (next_offset as usize) < index_size + 4 {
370 return Err(anyhow::anyhow!("Invalid next_offset in DAT archive"));
371 }
372 let first_size = reader.cpeek_u32_at(name_len as u64)?;
373 let second_offset = reader.cpeek_u32_at(8 + name_len as u64 * 2)?;
374 if second_offset < next_offset || second_offset - next_offset == first_size {
375 return Err(anyhow::anyhow!("Invalid second_offset in DAT archive"));
376 }
377 for i in 0..mcount {
378 let offset = next_offset;
379 if i + 1 == mcount {
380 break;
381 } else {
382 next_offset = reader.cpeek_u32_at((name_len as u64 + 4) * (i as u64 + 2))?;
383 }
384 if next_offset < offset {
385 return Err(anyhow::anyhow!("Invalid offset in DAT archive"));
386 }
387 if offset < index_size as u32 {
388 return Err(anyhow::anyhow!(
389 "Offset is less than index size in DAT archive"
390 ));
391 }
392 }
393 Ok(score)
394}
395
396pub fn is_this_format(buf: &[u8]) -> Result<u8> {
400 for &name_len in &NAME_LEN {
401 match is_this_format_name_len(buf, name_len) {
402 Ok(score) => return Ok(score),
403 Err(_) => continue,
404 }
405 }
406 Err(anyhow::anyhow!("Not a valid DAT archive format"))
407}